你可能没有实现一个正确的atoi函数
前言
我们都知道,atoi函数用于将一个字符串转换成整数。atoi函数看起来似乎很容易实现,你甚至可以很快写出一个版本,但是是否符合要求呢?
简易版本
最简单的考虑,就是遍历字符串,每遇到一个数字就加上原来的值乘以10。例如字符串“1234”转整数是这样的计算流程:
遇到字符1,得到结果1;
遇到字符2,得到结果1 * 10 + 2,即12;
遇到字符3,得到结果12 * 10 + 3,即123;
遇到字符4,得到结果123 * 10 + 4,即1234;
代码实现如下:
int my_atoi(const char *str)
{
if(NULL == str)
return 0;
int ret = 0;
while(0 != *str)
{
ret = ret * 10 + *str - '0';
str++;
}
return ret;
}
看起来既简洁又没有什么问题,输入数值时也似乎能得到正确结果。真的是这样吗?如果传入以下字符串参数,会是什么结果呢?
"-1"
"+1"
" "
"111111111111"
""
"1aab"
是不是发现并不是想象中的那样?那么实现atoi到底需要注意什么呢?
实现atoi函数需要注意什么
你可能已经注意到了,实现atoi需要考虑下面这些场景:
输入正负号
开头有空格
转换后的数值超出int的表示范围
出错时返回0与正确转换0的区别
输入非数字
空字符串
现在来看,前面的实现还能满足要求吗?
再次实现
那么重新考虑上面的要求,我们如何实现呢?我们需要考虑以下几种情况
如果开头是负号,则标记为负数;正号或数值,则标记为正数
跳过开头的空格,从第一个有效字符开始
使用更大类型存储计算值,如果负数比INT_MIN还小或正数比INT_MAX还大,则表明溢出,返回INT_MIN或INT_MAX,或者在下次计算之前与INT_MIN/10或INT_MAX/10比较
使用全局变量记录出错情况,区别正常转换为0或最大最小值
遇到非数值时即退出
根据上面这些考虑,我们重新实现代码:
int errCode = 0;
int my_atoi(char* str) {
int negative = 0;
char c = 0;
/*计算结果存储*/
long long ret = 0;
errCode = 0;
if(NULL == str)
{
errCode = NULL_PTR_ERR;
return 0;
}
/*跳过开始的空格*/
while(' ' == (*str))
str++;
/*跳过空格之后,到达了字符串结尾,则退出*/
if(0 == *str)
return 0;
/*负数*/
if(*str == '-' )
{
negative = 1;
str++;
}
/*正数*/
else if(*str == '+')
{
negative = 0;
str++;
}
/*正数*/
else if(isdigit(*str))
{
negative = 0;
}
/*如果不是以上内容,则直接退出*/
else
{
return 0;
}
while(isdigit(*str))
{
/*计算结果*/
ret = ret*10 + *str -'0';
/*如果发现结果大于INT_MAX或者小于INT_MIN,则溢出,返回最值*/
if(ret > (negative?-(long long )INT_MIN:INT_MAX))
{
/*溢出,返回最大值*/
errCode = ERANGE;
return negative?INT_MIN:INT_MAX;
}
str++;
}
/*根据正负号返回正确的结果*/
return negative?-ret:ret;
}
int main(void)
{
/*只有一个负号*/
int result = my_atoi("-");
printf("-:%d,errCode:%d\n",result,errCode);
/*空指针*/
result = my_atoi(NULL);
printf("NULL:%d,errCode:%d\n",result,errCode);
/*空字符串*/
result = my_atoi(" ");
printf(" :%d,errCode:%d\n",result,errCode);
/*负数*/
result = my_atoi("-1");
printf(" -1:%d,errCode:%d\n",result,errCode);
/*负数溢出*/
result = my_atoi(" -11111111111");
printf(" -11111111111:%d,errCode:%d\n",result,errCode);
/*正数*/
result = my_atoi("+123");
printf("+123:%d,errCode:%d\n",result,errCode);
/*正数溢出*/
result = my_atoi("+123111111111111111");
printf("+123111111111111111:%d,errCode:%d\n",result,errCode);
return 0;
}
运行结果:
-:0,errCode:0
NULL:0,errCode:1
:0,errCode:0
-1:-1,errCode:0
-11111111111:-2147483648,errCode:2
+123:123,errCode:0
+123111111111111111:2147483647,errCode:2
总结
上面的代码中errCode的设置需要根据需求而定,例如如果认为空字符串或只有负号转换是非法的,那么前面的代码将不符合要求。但这些都不是重点,重点是我们在考虑实现atoi函数的时候,需要考虑多种异常场景,这在平常实现其他功能接口的时候也是一样的。
思考
前面的代码有什么不足?你忽略了哪些场景?
关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。后台免费获取经典电子书和视频资源。